#include "Game.h"
#include <time.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <math.h>
#include "Renderer.h"
#include "TextureManager.h"
#include "Player.h"
#include "Renderer.h"
#include "TextLog.h"
#include "Enum.h"
#include "Keyboard.h"
#include "GameState.h"
#include "RecipeManager.h"
#include "StringLib.h"
#include "Dungeon.h"
#include "Constants.h"
#include "ObjectDatabase.h"
#include "Recipe.h"
#include "ItemStack.h"
#include "Level.h"
#include "Tile.h"

using namespace std;

Level* g_currentLevel;
Player* g_player;
Options g_options;
Renderer* g_renderer;
TextLog* g_textLog;
std::stack<GameMode> g_gameMode;
Keyboard* g_keyboard;
GameState* g_gameState;
RecipeManager* g_recipeManager;
StringLib* g_stringLib;
Dungeon* g_currentDungeon;
time_t g_seed;
ObjectDatabase* g_objectDatabase;
std::map<TypeHierarchy, std::map<int, bool>> g_idDatabase;
std::string g_currDirectory;
std::string m_prevFilename;

void display() {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	switch(g_gameMode.top()) {
	case GM_TITLE:
		g_renderer->renderTitle(0.0f, 0.0f, g_options.o_width, g_options.o_height);
		break;
	case GM_LOAD:
		g_renderer->renderLoad(g_keyboard->getLoadGames(), 0.0f, 0.0f, g_options.o_width, g_options.o_height);
		break;
	default:
	case GM_RANGED_TARGET:
	case GM_FOLLOW_PLAYER:
	case GM_PICK_UP:
	case GM_EXAMINE_LEVEL:
	case GM_DEAD:
	case GM_KICK:
	case GM_LOCK_UNLOCK: {
		int width = int(std::floor(SIDE_BAR_WIDTH / float(TILE_WIDTH) + 0.5f) * TILE_WIDTH);
		g_renderer->render(*g_currentLevel, 0.0f, 0.0f, g_options.o_width - width, g_options.o_height-NUM_TEXT_LOG_LINES*(g_options.o_fontSize+1));
		g_renderer->render(*g_textLog, 0.0f, g_options.o_height-NUM_TEXT_LOG_LINES*(g_options.o_fontSize+1), g_options.o_width, NUM_TEXT_LOG_LINES*(g_options.o_fontSize+1));
		g_renderer->renderPlayerInfo(*g_player, g_options.o_width - width, 0, width, g_options.o_height - NUM_TEXT_LOG_LINES*(g_options.o_fontSize+1));
		break;
	}
	case GM_SHOW_TEXT_LOG: {
		g_renderer->render(*g_textLog, 0.0f, 0.0f, g_options.o_width, g_options.o_height);
		break;
	}
	case GM_SHOW_ITEM_STACK:
	case GM_DROP_ITEMS:
	case GM_PICK_UP_MULTI:
	case GM_LOOT_CHEST:
	case GM_SELECT_QUAFF_ITEM:
	case GM_SELECT_RECIPE_ITEMS:
	case GM_SELECT_FOOD:
	case GM_SELECT_SCROLL:
	case GM_SELECT_SCROLL_ITEM:
	case GM_SELECT_EQUIPMENT_ITEM: {
		g_renderer->render(*(g_keyboard->getSelectedItemStack()), 0.0f, 0.0f, g_options.o_width, g_options.o_height);
		break;
	}
	case GM_SHOW_OBJECT: {
		TextLog textLog(g_options.o_height, false);
		g_keyboard->getSelectedObject()->logDetails(textLog);
		g_renderer->render(textLog, 0.0f, 0.0f, g_options.o_width, g_options.o_height);
		break;
	}
	case GM_SHOW_EQUIPMENT: {
		g_renderer->render(*(g_player->getEquipment()), 0.0f, 0.0f, g_options.o_width, g_options.o_height);
		break;
	}
	case GM_SELECT_RECIPE: {
		g_renderer->render(*g_recipeManager, 0.0f, 0.0f, g_options.o_width, g_options.o_height);
		break;
	}
	case GM_VIEW_RECIPE: {
		g_renderer->render(*g_keyboard->getSelectedRecipe(), 0.0f, 0.0f, g_options.o_width, g_options.o_height);
		break;
	}
	case GM_SELECT_TALENT: {
		g_keyboard->initTalentList(g_player);
		g_renderer->render(*g_keyboard->getAvailableTalents(), 0.0f, 0.0f, g_options.o_width, g_options.o_height);
		break;
	}
	case GM_SELECT_SKILL: {
		g_renderer->renderSelectSkill(g_player->getSkills(), 0.0f, 0.0f, g_options.o_width, g_options.o_height);
		break;
	}
	case GM_SELECT_NEW_SKILL: {
		g_renderer->renderSelectNewSkill(g_player->getSkills(), 0.0f, 0.0f, g_options.o_width, g_options.o_height);
		break;
	}
	}

	glFlush();
	glutSwapBuffers();
}

void reshape(int width,int height) {
	glViewport(0,0,width,height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0.0f,width,0,height,-1,1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void firstLoad();

int main(int argc,char* argv[]) {
	g_currDirectory = argv[0];
	char substringChar = '/';
	if(g_currDirectory.find_last_of('/') == -1) {
		substringChar = '\\';
	}
	g_currDirectory = g_currDirectory.substr(0, g_currDirectory.find_last_of(substringChar)+1);

	g_options.o_width = 1024;
	g_options.o_height = 768;
	g_options.o_fontSize = 15;
	g_options.o_name = "playerName";

	glutInit(&argc,argv);

	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(g_options.o_width, g_options.o_height);
	glutCreateWindow("Game");
	glutDisplayFunc(display);
	//glutIdleFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(Keyboard::handleInput);
	glutSpecialFunc(Keyboard::handleSpecInput);
	//glutMouseFunc(mouseClick);
	//glutPassiveMotionFunc(mouseMove);

	firstLoad();

	glutMainLoop();
	return 0;
}

void firstLoad() {
	m_prevFilename = "";
	g_seed = time(NULL);
	srand(g_seed);
	ItemStack::initGameData();
	g_stringLib = new StringLib(LANG_ENG);
	g_stringLib->setPlayerName(g_options.o_name);
	g_objectDatabase = new ObjectDatabase();
	g_renderer = new Renderer(new TextureManager());
	g_keyboard = new Keyboard();
	g_gameMode.push(GM_TITLE);
}

void newGame () {
	g_gameMode.pop();
	g_gameMode.push(GM_FOLLOW_PLAYER);
	g_gameState = new GameState();
	g_textLog = new TextLog(g_options.o_height);
	g_textLog->addLine(g_stringLib->getTextString(ST_WELCOME));
	g_player = new Player(PT_DEFAULT_PLAYER);
	Dungeon* dungeon = new Dungeon(DT_DUNGEON, 10, 1);
	dungeon->enterDungeon(g_player);
	g_recipeManager = new RecipeManager();
}

void clearGame() {
	g_keyboard->resetKeyboard();
	while(g_gameMode.size() > 0)
		g_gameMode.pop();
	g_gameMode.push(GM_TITLE);
	if(g_gameState)
		delete g_gameState;
	if(g_textLog)
		delete g_textLog;
	if(g_currentLevel)
		delete g_currentLevel->getDungeon();
	if(g_recipeManager)
		delete g_recipeManager;
}

void saveGame() {
	std::ofstream file;
	std::string filename = g_currDirectory + "saves/" + g_player->getName() + "_-_L" + Util::intToString(g_player->getLevel()) + "_" +
		g_stringLib->getMiscString(MST_DUNGEON, g_player->getTile()->getLevel()->getDungeon()->getDungeonType(), 1) + Util::intToString(g_player->getTile()->getLevel()->getLevelNum()+1) + "_" + Util::intToString(time(NULL));
	file.open(filename);
	//file << g_seed << "\n";
	g_objectDatabase->save(file);
	// id database
	for(std::map<TypeHierarchy, std::map<int, bool>>::iterator iter = g_idDatabase.begin(); iter != g_idDatabase.end(); iter++) {
		for(std::map<int, bool>::iterator iter2 = (*iter).second.begin(); iter2 != (*iter).second.end(); iter2++) {
			file << (*iter).first << " " << (*iter2).first << " " << ((*iter2).second ? 1 : 0) << "\n";
		}
	}
	g_textLog->save(file);
	g_currentLevel->getDungeon()->save(file);
	file.close();
	deleteOldGame();
	m_prevFilename = filename;
}

void loadGame(std::string filename) {
	g_gameMode.pop();
	g_gameMode.push(GM_FOLLOW_PLAYER);
	g_gameState = new GameState();
	g_textLog = new TextLog(g_options.o_height);
	
	g_recipeManager = new RecipeManager();
	filename = g_currDirectory + "saves/" + filename;
	std::ifstream file(filename);
	if(file.is_open()) {
		std::string line;
		//getline(file, line); // seed
		g_objectDatabase->load(file);
		for(std::map<TypeHierarchy, std::map<int, bool>>::iterator iter = g_idDatabase.begin(); iter != g_idDatabase.end(); iter++) {
			for(std::map<int, bool>::iterator iter2 = (*iter).second.begin(); iter2 != (*iter).second.end(); iter2++) {
				getline(file, line);
				int* tokens = Util::intSplit(line);
				g_idDatabase[TypeHierarchy(tokens[0])][tokens[1]] = (tokens[2] == 1 ? true : false);
				delete[] tokens;
			}
		}
		g_textLog->load(file);
		g_textLog->addLine(g_stringLib->getTextString(ST_WELCOME2));

		Dungeon* dungeon = new Dungeon(file);
		
		g_currentLevel = g_player->getTile()->getLevel();
		g_currentDungeon = g_currentLevel->getDungeon();
		g_gameState->updateCharacterList();
		g_player->calculateFOV();
		file.close();
		m_prevFilename = filename;
	} else {
		exit(-1);
	}
}

void deleteOldGame() {
	if(m_prevFilename.compare("")) {
		cout << remove(m_prevFilename.c_str()) << endl;
	}
}
